Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.02% covered (success)
99.02%
101 / 102
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApieServiceProvider
99.02% covered (success)
99.02%
101 / 102
75.00% covered (warning)
75.00%
3 / 4
21
0.00% covered (danger)
0.00%
0 / 1
 autoTagHashmapActions
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
4.00
 boot
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 register
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
1 / 1
9
 sanitizeConfig
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2namespace Apie\LaravelApie;
3
4use Apie\ApieCommonPlugin\ApieCommonPluginServiceProvider;
5use Apie\CmsApiDropdownOption\CmsDropdownServiceProvider;
6use Apie\Common\CommonServiceProvider;
7use Apie\Common\Interfaces\BoundedContextSelection;
8use Apie\Common\Interfaces\DashboardContentFactoryInterface;
9use Apie\Common\Wrappers\BoundedContextHashmapFactory;
10use Apie\Common\Wrappers\ConsoleCommandFactory as CommonConsoleCommandFactory;
11use Apie\LaravelApie\Config\LaravelConfiguration;
12use Illuminate\Config\Repository;
13use Symfony\Component\Config\ConfigCache;
14use Symfony\Component\Config\Definition\Processor;
15
16;
17use Apie\Console\ConsoleServiceProvider;
18use Apie\Core\CoreServiceProvider;
19use Apie\Core\Session\CsrfTokenProvider;
20use Apie\DoctrineEntityConverter\DoctrineEntityConverterProvider;
21use Apie\DoctrineEntityDatalayer\DoctrineEntityDatalayerServiceProvider;
22use Apie\Faker\FakerServiceProvider;
23use Apie\HtmlBuilders\ErrorHandler\CmsErrorRenderer;
24use Apie\HtmlBuilders\HtmlBuilderServiceProvider;
25use Apie\LaravelApie\ContextBuilders\CsrfTokenContextBuilder;
26use Apie\LaravelApie\ContextBuilders\RegisterBoundedContextActionContextBuilder;
27use Apie\LaravelApie\ContextBuilders\SessionContextBuilder;
28use Apie\LaravelApie\ErrorHandler\ApieErrorRenderer;
29use Apie\LaravelApie\ErrorHandler\Handler;
30use Apie\LaravelApie\Providers\CmsServiceProvider;
31use Apie\LaravelApie\Providers\SecurityServiceProvider;
32use Apie\LaravelApie\Wrappers\Cms\DashboardContentFactory;
33use Apie\LaravelApie\Wrappers\Core\BoundedContextSelected;
34use Apie\Maker\MakerServiceProvider;
35use Apie\RestApi\RestApiServiceProvider;
36use Apie\SchemaGenerator\SchemaGeneratorServiceProvider;
37use Apie\Serializer\SerializerServiceProvider;
38use Apie\ServiceProviderGenerator\TagMap;
39use Illuminate\Contracts\Debug\ExceptionHandler;
40use Illuminate\Contracts\Events\Dispatcher;
41use Illuminate\Support\ServiceProvider;
42use Psr\EventDispatcher\EventDispatcherInterface;
43use Psr\Http\Message\ServerRequestInterface;
44use Symfony\Component\Config\Resource\ReflectionClassResource;
45use Symfony\Component\Console\Application;
46
47class ApieServiceProvider extends ServiceProvider
48{
49    /**
50     * @var array<string, array<int, class-string<ServiceProvider>>> $dependencies
51     */
52    private array $dependencies = [
53        'enable_common_plugin' => [
54            ApieCommonPluginServiceProvider::class,
55        ],
56        'enable_cms' => [
57            CommonServiceProvider::class,
58            HtmlBuilderServiceProvider::class, // it's important that this loads before CmsServiceProvider!!!
59            CmsServiceProvider::class,
60            SerializerServiceProvider::class,
61        ],
62        'enable_cms_dropdown' => [
63            CommonServiceProvider::class,
64            CmsDropdownServiceProvider::class,
65        ],
66        'enable_core' => [
67            CoreServiceProvider::class,
68        ],
69        'enable_console' => [
70            CommonServiceProvider::class,
71            ConsoleServiceProvider::class,
72            SerializerServiceProvider::class,
73        ],
74        'enable_doctrine_entity_converter' => [
75            CoreServiceProvider::class,
76            DoctrineEntityConverterProvider::class,
77        ],
78        'enable_doctrine_entity_datalayer' => [
79            CoreServiceProvider::class,
80            DoctrineEntityConverterProvider::class,
81            DoctrineEntityDatalayerServiceProvider::class,
82        ],
83        'enable_security' => [
84            CommonServiceProvider::class,
85            SerializerServiceProvider::class,
86            SecurityServiceProvider::class,
87        ],
88        'enable_rest_api' => [
89            CommonServiceProvider::class,
90            RestApiServiceProvider::class,
91            SchemaGeneratorServiceProvider::class,
92            SerializerServiceProvider::class,
93        ],
94        'enable_faker' => [
95            FakerServiceProvider::class,
96        ],
97        'enable_maker' => [
98            MakerServiceProvider::class,
99        ],
100    ];
101
102    private function autoTagHashmapActions(): void
103    {
104        $boundedContextConfig = config('apie.bounded_contexts');
105        $scanBoundedContextConfig = config('apie.scan_bounded_contexts');
106        $factory = new BoundedContextHashmapFactory($boundedContextConfig ?? [], $scanBoundedContextConfig ?? []);
107        $hashmap = $factory->create();
108        foreach ($hashmap as $boundedContext) {
109            foreach ($boundedContext->actions as $action) {
110                $class = $action->getDeclaringClass();
111                if (!$class->isInstantiable()) {
112                    continue;
113                }
114                $className = $class->name;
115                TagMap::register(
116                    $this->app,
117                    $className,
118                    ['apie.context']
119                );
120            }
121        }
122    }
123
124    public function boot(): void
125    {
126        $this->autoTagHashmapActions();
127        $this->loadViewsFrom(__DIR__ . '/../templates', 'apie');
128        $this->loadRoutesFrom(__DIR__.'/../resources/routes.php');
129        TagMap::registerEvents($this->app);
130        if ($this->app->runningInConsole()) {
131            $commands = [];
132            // for some reason these are not called in integration tests without re-registering them
133            foreach (TagMap::getServiceIdsWithTag($this->app, 'console.command') as $taggedCommand) {
134                $serviceId = 'apie.console.tagged.' . $taggedCommand;
135                $this->app->singleton($serviceId, function () use ($taggedCommand) {
136                    return $this->app->get($taggedCommand);
137                });
138                $commands[] = $serviceId;
139            }
140            /** @var CommonConsoleCommandFactory $factory */
141            $factory = $this->app->get('apie.console.factory');
142            foreach ($factory->create($this->app->get(Application::class)) as $command) {
143                $serviceId = 'apie.console.registered.' . $command->getName();
144                $this->app->instance($serviceId, $command);
145                $commands[] = $serviceId;
146            }
147            $this->commands($commands);
148        }
149    }
150
151    public function register()
152    {
153        $this->mergeConfigFrom(__DIR__ . '/../resources/apie.php', 'apie');
154
155        // add PSR-14 support if needed:
156        if (!$this->app->bound(EventDispatcherInterface::class)) {
157            $this->app->bind(EventDispatcherInterface::class, function () {
158                return new class($this->app->make(Dispatcher::class)) implements EventDispatcherInterface {
159                    public function __construct(private readonly Dispatcher $dispatcher)
160                    {
161                    }
162
163                    public function dispatch(object $event): object
164                    {
165                        $this->dispatcher->dispatch($event);
166                        return $event;
167                    }
168                };
169            });
170        }
171
172        // fix for https://github.com/laravel/framework/issues/30415
173        $this->app->extend(
174            ServerRequestInterface::class,
175            function (ServerRequestInterface $psrRequest) {
176                $route = $this->app->make('request')->route();
177                if ($route) {
178                    $parameters = $route->parameters();
179                    foreach ($parameters as $key => $value) {
180                        $psrRequest = $psrRequest->withAttribute($key, $value);
181                    }
182                }
183                return $psrRequest;
184            }
185        );
186
187        $this->app->bind(ApieErrorRenderer::class, function () {
188            return new ApieErrorRenderer(
189                $this->app->bound(CmsErrorRenderer::class) ? $this->app->make(CmsErrorRenderer::class) : null,
190                $this->app->make(\Apie\Common\ErrorHandler\ApiErrorRenderer::class),
191                config('apie.cms.base_url')
192            );
193        });
194
195        $this->app->extend(ExceptionHandler::class, function (ExceptionHandler $service) {
196            return new Handler($this->app, $service);
197        });
198        
199        $this->app->bind(DashboardContentFactoryInterface::class, DashboardContentFactory::class);
200        $this->app->bind(BoundedContextSelection::class, BoundedContextSelected::class);
201
202        $alreadyRegistered = [];
203        foreach ($this->dependencies as $configKey => $dependencies) {
204            if (config('apie.' . $configKey, false)) {
205                foreach ($dependencies as $dependency) {
206                    if (!isset($alreadyRegistered[$dependency])) {
207                        $alreadyRegistered[$dependency] = $dependency;
208                        $this->app->register($dependency);
209                    }
210                }
211            }
212        }
213        //$this->app->bind(CsrfTokenProvider::class, CsrfTokenContextBuilder::class);
214        TagMap::register($this->app, CsrfTokenContextBuilder::class, ['apie.core.context_builder']);
215        $this->app->tag(CsrfTokenContextBuilder::class, ['apie.core.context_builder']);
216
217        // this has to be added after CsrfTokenContextBuilder!
218        $this->app->bind(SessionContextBuilder::class);
219        TagMap::register($this->app, SessionContextBuilder::class, ['apie.core.context_builder']);
220        $this->app->tag(SessionContextBuilder::class, ['apie.core.context_builder']);
221
222        TagMap::register($this->app, RegisterBoundedContextActionContextBuilder::class, ['apie.core.context_builder']);
223        $this->app->tag(RegisterBoundedContextActionContextBuilder::class, ['apie.core.context_builder']);
224        $this->app->extend('config', function (Repository $config) {
225            $this->sanitizeConfig($config);
226            return $config;
227        });
228    }
229
230    private function sanitizeConfig(Repository $config): void
231    {
232        $rawConfig = $config->get('apie');
233        $path = storage_path('framework/cache/apie-config' . md5(json_encode($rawConfig)) . '.php');
234        $resources = [
235            new ReflectionClassResource(new \ReflectionClass(LaravelConfiguration::class)),
236            new ReflectionClassResource(new \ReflectionClass(static::class)),
237        ];
238        $configCache = new ConfigCache($path, true);
239        if ($configCache->isFresh()) {
240            $processedConfig = require $path;
241        } else {
242            $configuration = new LaravelConfiguration();
243
244            $processor = new Processor();
245
246            $processedConfig = $processor->processConfiguration($configuration, ['apie' => $rawConfig]);
247
248            if (!isset($processedConfig['scan_bounded_contexts'])) {
249                $processedConfig['scan_bounded_contexts'] = [];
250            }
251            if (empty($processedConfig['storage'])) {
252                $processedConfig['storage'] = null;
253            }
254            $code = '<?php' . PHP_EOL . 'return ' . var_export($processedConfig, true) . ';';
255            $configCache->write($code, $resources);
256        }
257
258        $config->set('apie', $processedConfig);
259    }
260}